package com.example.demo.saturn;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import org.slf4j.MDC;

import javax.net.ssl.SSLContext;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

/**
 * ClassName: HttpUtil <br/>
 * Function: TODO ADD FUNCTION. <br/>
 * Reason: TODO ADD REASON(可选). <br/>
 * date: 2014年11月28日 上午10:11:02 <br/>
 *
 * @author 李鑫
 * @version
 * @since JDK 1.7
 */

@Slf4j
public final class HttpUtil {

	private static String ISRSA = "0"; //o2oapi项目中使用的加密头

	/* 链路ID */
	public static final String TRACE_ID = "SESSION_ID";
	public static final String SPAN_ID = "REQUEST_ID";
	public static final String SOFA_TRACE_ID = "SOFA-TraceId";
	public static final String SOFA_SPAN_ID = "SOFA-SpanId";
	public static final String X_B3_TRACE_ID = "X-B3-TraceId";
	public static final String X_B3_SPAN_ID = "X-B3-SpanId";
	private static final String TCSL_SHARDINGKEY = "Tcsl-Shardingkey";
	private static final String TCSL_PRODUCTCODE = "Tcsl-ProductCode";
	private static final String TCSL_PRODUCTCODE_VALUE = "100000";
	private static final String TCSL_PRODUCTVERSION = "Tcsl-productVersion";
	private static final String TCSL_PRODUCTVERSION_VALUE = "1.0.0";

//	private static PoolingHttpClientConnectionManager cm = null;//httpClinet的连接池

	public static final Map<Integer, String> HTTP_RETURN_CODE = new HashMap<Integer, String>();

	//httpClient的最大请求并发数,cpu数*2+2,太大的话，创建和回收线程都需要时间，反而性能不好
	private static final int HTTP_CLIENT_MAX_THREAD_COUNTS = 10;

	static {
		HTTP_RETURN_CODE.put(100, "Continue");
		HTTP_RETURN_CODE.put(101, "Switching Protocols");
		HTTP_RETURN_CODE.put(102, "Processing");

		HTTP_RETURN_CODE.put(200, "OK");
		HTTP_RETURN_CODE.put(201, "Created");
		HTTP_RETURN_CODE.put(202, "Accepted");
		HTTP_RETURN_CODE.put(203, "Non-Authoritative Information");
		HTTP_RETURN_CODE.put(204, "No Content");
		HTTP_RETURN_CODE.put(205, "Reset Content");
		HTTP_RETURN_CODE.put(206, "Partial Content");
		HTTP_RETURN_CODE.put(207, "Multi-Status");

		HTTP_RETURN_CODE.put(300, "Multiple Choices");
		HTTP_RETURN_CODE.put(301, "Moved Permanently");
		HTTP_RETURN_CODE.put(302, "Move temporarily");
		HTTP_RETURN_CODE.put(303, "See Other");
		HTTP_RETURN_CODE.put(304, "Not Modified");
		HTTP_RETURN_CODE.put(305, "Use Proxy");
		HTTP_RETURN_CODE.put(306, "Switch Proxy");
		HTTP_RETURN_CODE.put(307, "Temporary Redirect");

		HTTP_RETURN_CODE.put(400, "Bad Request");
		HTTP_RETURN_CODE.put(401, "Unauthorized");
		HTTP_RETURN_CODE.put(402, "Payment Required");
		HTTP_RETURN_CODE.put(403, "Forbidden");
		HTTP_RETURN_CODE.put(404, "Not Found");
		HTTP_RETURN_CODE.put(405, "Method Not Allowed");
		HTTP_RETURN_CODE.put(406, "Not Acceptable");
		HTTP_RETURN_CODE.put(407, "Proxy Authentication Required");
		HTTP_RETURN_CODE.put(408, "Request Timeout");
		HTTP_RETURN_CODE.put(409, "Conflict");
		HTTP_RETURN_CODE.put(410, "Gone");
		HTTP_RETURN_CODE.put(411, "Length Required");
		HTTP_RETURN_CODE.put(412, "Precondition Failed");
		HTTP_RETURN_CODE.put(413, "Request Entity Too Large");
		HTTP_RETURN_CODE.put(414, "Request-URI Too Long");
		HTTP_RETURN_CODE.put(415, "Unsupported Media Type");
		HTTP_RETURN_CODE.put(416, "Requested Range Not Satisfiable");
		HTTP_RETURN_CODE.put(417, "Expectation Failed");
		HTTP_RETURN_CODE.put(421, "There are too many connections from your internet address");
		HTTP_RETURN_CODE.put(422, "Unprocessable Entity");
		HTTP_RETURN_CODE.put(423, "Locked");
		HTTP_RETURN_CODE.put(424, "Failed Dependency");
		HTTP_RETURN_CODE.put(425, "Unordered Collection");
		HTTP_RETURN_CODE.put(426, "Upgrade Required");
		HTTP_RETURN_CODE.put(449, "Retry With");

		HTTP_RETURN_CODE.put(500, "Internal Server Error");
		HTTP_RETURN_CODE.put(501, "Not Implemented");
		HTTP_RETURN_CODE.put(502, "Bad Gateway");
		HTTP_RETURN_CODE.put(503, "Service Unavailable");
		HTTP_RETURN_CODE.put(504, "Gateway Timeout");
		HTTP_RETURN_CODE.put(505, "HTTP Version Not Supported");
		HTTP_RETURN_CODE.put(506, "Variant Also Negotiates");
		HTTP_RETURN_CODE.put(507, "Insufficient Storage");
		HTTP_RETURN_CODE.put(509, "Bandwidth Limit Exceeded");
		HTTP_RETURN_CODE.put(510, "Not Extended");

		HTTP_RETURN_CODE.put(600, "Unparseable Response Headers");
	}


	private static String urlTransCodeing(String url) {
		return url.replace("\r\n", "").replace("\"", "%22").replace("{", "%7b").replace("}", "%7d").replace("|", "%124"); //.replace("&", "%26")
	}

	/*	private static SSLConnectionSocketFactory getSSLConnectionSocketFactory() throws Exception {
			TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
				@Override
				public java.security.cert.X509Certificate[] getAcceptedIssuers() {
					return null;
				}
				@Override
				public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
				}
				@Override
				public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
				}
			}};
			SSLContext sc = SSLContext.getInstance("TLSv1");
	//		SSLContext sc = SSLContexts.custom().useTLS().build();
			sc.init(null, trustAllCerts, new java.security.SecureRandom());
			// return new SSLConnectionSocketFactory(sc, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
			return new SSLConnectionSocketFactory(sc, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
		}*/

	private static SSLConnectionSocketFactory getSSLConnectionSocketFactory() throws Exception {
		try {
			SSLContext sc = SSLContextBuilder.create().build();
			return new SSLConnectionSocketFactory(sc, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
		} catch (Exception e) {
			log.error("getSSLConnectionSocketFactory异常", e);
			return null;
		}
	}

	/*private static ConnectionKeepAliveStrategy keepAliveStrategy = new DefaultConnectionKeepAliveStrategy() {
	    @Override
	    public long getKeepAliveDuration(
	        HttpResponse response, HttpContext context) {
	            long keepAlive = super.getKeepAliveDuration(response, context);
	            if (keepAlive == -1) {
	            	// 如果服务器没有设置keep-alive这个参数，我们就把它设置成5秒
	            	System.out.println("keepAlive==-1" );
	                keepAlive = 2000;
	            }
	            return keepAlive;
	    }
	};*/


	/*
	 * setConnectTimeout：设置连接超时时间，单位毫秒
	 * setConnectionRequestTimeout：设置从connect Manager(连接池)获取Connection 超时时间，单位毫秒。这个属性是新加的属性，因为目前版本是可以共享连接池的
	 * setSocketTimeout：请求获取数据的超时时间(即响应时间)，单位毫秒。 如果访问一个接口，多少时间内无法返回数据，就直接放弃此次调用。
	 */
	public static CloseableHttpClient getSslHttpClient() throws Exception {
		RequestConfig config = RequestConfig.custom().setConnectTimeout(20000).setSocketTimeout(10000).build();
		//CloseableHttpClient httpclient  = HttpClients.custom().setSSLSocketFactory(getSSLConnectionSocketFactory()).setDefaultRequestConfig(config).setKeepAliveStrategy(keepAliveStrategy).build();
		CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(getSSLConnectionSocketFactory()).setDefaultRequestConfig(config).build();
		return httpclient;
	}


	private static String getRequestURL(String url) {
		int idx = url.indexOf("?");
		if (idx > 0) {
			return url.substring(0, idx).intern();
		}
		return url.intern();
	}


	/**
	 *
	 * doPost:(Post请求数据流). <br/>
	 * Date:   2015年5月5日 上午10:44:37 <br/>
	 * @author 李鑫
	 * @param url
	 * @param postData
	 * @param contentType
	 * @return
	 * @throws Throwable
	 * @throws Exception
	 */
	private static String doPost(String url, String postData, String contentType, Map<String, String> zoneShardingParams) throws Exception {
		if (StringUtils.isBlank(url)) {
			throw new NullPointerException("URL请求地址为空!");
		}
		long start = System.nanoTime();
		url = urlTransCodeing(url);
		HttpPost httpPost = new HttpPost(url);

		httpPost.addHeader("isrsa", HttpUtil.ISRSA);
		httpPost.addHeader(HttpUtil.TRACE_ID, (StringUtils.isNotEmpty(MDC.get(HttpUtil.TRACE_ID)) ? MDC.get(HttpUtil.TRACE_ID) : StringUtils.EMPTY));
		httpPost.addHeader(HttpUtil.SOFA_TRACE_ID, (StringUtils.isNotEmpty(MDC.get(HttpUtil.TRACE_ID)) ? MDC.get(HttpUtil.TRACE_ID) : StringUtils.EMPTY));
		httpPost.addHeader(HttpUtil.X_B3_TRACE_ID, (StringUtils.isNotEmpty(MDC.get(HttpUtil.TRACE_ID)) ? MDC.get(HttpUtil.TRACE_ID) : StringUtils.EMPTY));
		httpPost.addHeader(HttpUtil.SPAN_ID, (StringUtils.isNotEmpty(MDC.get(HttpUtil.SPAN_ID)) ? MDC.get(HttpUtil.SPAN_ID) : StringUtils.EMPTY));
		httpPost.addHeader(HttpUtil.SOFA_SPAN_ID, (StringUtils.isNotEmpty(MDC.get(HttpUtil.SPAN_ID)) ? MDC.get(HttpUtil.SPAN_ID) : StringUtils.EMPTY));
		httpPost.addHeader(HttpUtil.X_B3_SPAN_ID, (StringUtils.isNotEmpty(MDC.get(HttpUtil.SPAN_ID)) ? MDC.get(HttpUtil.SPAN_ID) : StringUtils.EMPTY));

		// 兼容云端分区键
		if (MapUtils.isNotEmpty(zoneShardingParams)) {
			for (Entry<String, String> zoneShardingParam : zoneShardingParams.entrySet()) {
				httpPost.addHeader(zoneShardingParam.getKey(), zoneShardingParam.getValue());
			}
		}

		StringEntity postDataEntity = new StringEntity(postData, "utf-8");//解决中文乱码问题
		postDataEntity.setContentEncoding("UTF-8");
		postDataEntity.setContentType(contentType);
		httpPost.setEntity(postDataEntity);
		try (CloseableHttpClient httpClient = getSslHttpClient(); CloseableHttpResponse response = httpClient.execute(httpPost);) {
			int statusCode = response.getStatusLine().getStatusCode();
			if (HttpStatus.SC_OK != statusCode) {
				httpPost.releaseConnection();
				Exception exception = new Exception("状态码=" + statusCode + ",描述=" + HTTP_RETURN_CODE.get(statusCode));
				throw exception;
			}
			HttpEntity entity = response.getEntity();
			String result = EntityUtils.toString(entity, "UTF-8");
			// EntityUtils.consume(entity);
			EntityUtils.consumeQuietly(entity);
			log.info("Post成功请求耗时{}毫秒,url={},zsp={},postData={},result={}", (TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)), url, JSON.toJSONString(zoneShardingParams), postData, result);
			return result;
		} catch (Throwable e) {
			log.error("Post失败请求耗时{}毫秒,url={},zsp={},postData={}", (TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)), url, JSON.toJSONString(zoneShardingParams), postData, e);
			throw e;
		}
	}


	/*
	 * 请求JSON数据
	 * */
	private static String doPostJson(String url, String postData, Map<String, String> zoneShardingParams) throws Exception {
		return doPost(url, postData, "application/json", zoneShardingParams);
	}

	/**
	 *
	 * doPostJSONObject:(这里用一句话描述这个方法的作用). <br/>
	 * Date:   2015年7月16日 上午11:40:33 <br/>
	 * @author 李鑫
	 * @param url
	 * @param postData
	 * @return
	 * @throws Exception
	 */
	public static JSONObject doPostJSONObject(String url, String postData) throws Exception {
		return JSON.parseObject(doPostJson(url, postData, Collections.emptyMap()));
	}

	public static JSONObject doPostJSONObject(String url, String postData, Map<String, String> headerMap) throws Exception {
		return JSON.parseObject(doPostJson(url, postData, headerMap));
	}


}
